import tkinter as tk
import numpy as np
import inspect, sys, traceback, os, re, plot
from code import InteractiveInterpreter
from util import vdict, open_editor, BUTTON_RIGHT, BASEPATH, FONT
from version import VERSION
from doc import Help
from style import Color, TAGS
from types import CodeType
from widget import Text, Menu
from .prompt import Prompt, FONTSIZE
from .codeparser import CodeParser
from .builtin_print import builtin_print

RE_TRACEBACK = r"^()(Traceback \(most recent call last\))"
RE_FILEPATH = r"(File (\"(?!<).*\"))"
RE_INPUT = r"(File \"(<.*>)\")"
RE_LINE = r"((line [0-9]*))"
RE_IN = r"line ([0-9]*), in (.*)"

class Interpreter(Text, InteractiveInterpreter):
	"""
	这个类从模块的代码中继承了' interactiveinterpreter '，并遵循了创建python解释器的官方模式。

	为了与解释器同步所有不同的视图，本地命名空间被存储在一个名为“self.locals”的vdict中。
	vdict是一个键-值对，就像一个普通的字典，不过它也还有一些事件回调的功能，比如设置和删除项。

	当运行命令时，' self.locals '作为第三个参数传递给内置的exec函数。
	此参数确定exec运行的本地命名空间。
	可以通过运行命令来查看本地命名空间的当前内容。

	>>> locals()

	当exec处理代码时，它会更新' self.locals '中的值。
	但由于这是vdict，回调函数会自动触发。

	曲尺控制台扩展了传统的python解释器，并且有很多的质量突破，比如语法高亮和各种各样的快捷键。
	在执行每个命令之前和之后，它将解析命令字符串，以使其他视图与本地名称空间中的变量保持同步。
	代码解析完成使用抽象语法树模块' ast'。
	python在底层生成一个抽象语法树，作为处理代码行和执行这些命令之间的中间步骤。
	有一个学习抽象语法树的很好的资源渠道：https://greentreesnakes.readthedocs.io/

	"""
	def __init__(self, app):	
		InteractiveInterpreter.__init__(self, vdict({
			"app": app,
			"plot": plot.plot
		}, setitem=self.setitem, delitem=self.delitem))

		self.frame = tk.Frame(app, 
			padx=16,
			pady=8,
			background=Color.DARK_BLACK)

		Text.__init__(self, self.frame,
			readonly=True, 
			background=Color.DARK_BLACK, 
			font=FONT,
			padx=0,
			pady=0,
			wrap="word",
			cursor="arrow", 
			insertbackground=Color.DARK_BLACK)

		sys.displayhook = self.write
		sys.excepthook = self.showtraceback
		__builtins__["print"] = self.write		
		__builtins__["help"] = Help(app)
		__builtins__["clear"] = Clear(self)
		__builtins__["copyright"] = "Copyright (c) 2021 Honglvdeng.\nAll Rights Reserved."
		__builtins__["credits"] = "Created by Honglvdeng."
		__builtins__["license"] = License()

		self.app = app
		self.prompt = Prompt(self, self.frame)
		self.parse = CodeParser(app)
		self.buffer = []
		self.prevent_module_import = False

		self.bind("<Key>", self._on_key_log)
		self.bind("<Configure>", self.prompt.on_configure_log)
		self.bind("<ButtonRelease-1>", self.on_click_log)
		self.pack(fill="both", expand=True)
		
		for i in ["error_file"]:
			self.tag_bind(i, "<Motion>", lambda event, key=i: self._motion(event, key))
			self.tag_bind(i, "<Leave>", lambda event, key=i: self._leave(event, key))
			self.tag_bind(i, "<Button-1>", lambda event, key=i: self._click(event, key))    

	def _init(self, event):
		self.config_count = self.config_count + 1 if hasattr(self, "config_count") else 1
		if self.config_count > 2:
			self.do_greet()
			self.prompt.place(width=self.winfo_width())
			self.bind("<Configure>", self.prompt.on_configure_log)
		
	def do_greet(self):
		self.write("QU CHI " + VERSION + " (Beta)\nType \"help\", \"copyright\", \"credits\", \"license\" for more information")	
		self.prompt()

	def setitem(self, key, value):
		if inspect.ismodule(value):
			if not self.prevent_module_import:
				self.app.modules[key] = value
		else:
			self.app.objects[key] = value

	def delitem(self, key, value):
		if inspect.ismodule(value):
			del self.app.modules[key]
		else:
			del self.app.objects[key]

	def synclocals(self):
		self.locals.store.update(self.app.objects.store)
		self.locals.store.update(self.app.modules.store)

	def eval(self, source):
		self.synclocals()
		return eval(source, self.locals)

	def exec(self, source, filename="<file>"):
		self.prevent_module_import = True
		self.synclocals()
		self.parse.preprocess(source)
		self.runsource(source, filename, "exec")
		self.parse.postprocess(source)
		self.prevent_module_import = False

	def push(self, s, filename="<input>", log=True, symbol="single"):
		self.synclocals()
		source = "".join(self.buffer + [s])
		self.parse.preprocess(source)
		if source[:4] == "plot":
			self.prompt()
		did_compile = self.runsource(source, filename, symbol)
		
		if did_compile:
			self.buffer.append(s + "\n")
			self.prompt.history.append(s)
		else:
			self.parse.postprocess(source)
			self.prompt.history.extend(s)
			self.buffer.clear()
		self.prompt()
	
	def write(self, *args, syntax_highlight=False, tags=(), **kwargs):
		idx = self.index("insert")
		for r in args:
			if re.match(RE_TRACEBACK, str(r)):
				tags = ("red", *tags)
			
			if r is not None:
				if isinstance(r, Exception):
					builtin_print("\a")
					tags = tuple(list(tags) + ["red"])
				self.insert("end", str(r), tags, syntax_highlight=syntax_highlight)
				if len(args) > 1:
					self.insert("end", "\t")
		
		try:
			self.highlight(RE_FILEPATH, "error_file", idx)
			self.highlight(RE_INPUT, "purple", idx)
			self.highlight(RE_LINE, "blue", idx)
			self.highlight(RE_IN, "green", idx)
		except:
			pass

		if self.get("1.0", "end").strip():
			self.insert("end", "\n")
		self.see("end")
		self.prompt.move()

	def showtraceback(self, *args):
		sys.last_type, sys.last_value, last_tb = ei = sys.exc_info()
		sys.last_traceback = last_tb

		try:
			lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next)
			self.write(''.join(lines).rstrip(), tags="red")
			builtin_print ("\a")
			self.app.menu.setview("console", True)
		finally:
			last_tb = ei = None

	def clear(self):
		self.prompt.is_on_bottom = False
		self.delete("1.0", "end")
	
	def _on_key_log(self, event):
		result = self._on_key(event)
		if result:
			self.prompt.focus()
		return result

	def on_click_log(self, event):
		 if not self.tag_ranges("sel"):
		 	self.prompt.focus()

	def _on_button_right(self, event):
		option = []
		tag_ranges = self.tag_ranges("sel")
		if tag_ranges:
			option.extend([{
				"label": "复制",
				"command": lambda: self.clipboard_append(self.get(*tag_ranges))
			}])

		self.menu.show(event, option + [{
			"label": "清除记录",
			"command": clear
		}])


	def _click(self, event, tag):
		if not self.hover_range: return
		content = self.get(*self.hover_range)
		if tag == "error_file":
			open_editor(os.path.abspath(content[1:-1]))

class License:
	def __call__(self):
		help(os.path.join(BASEPATH, "LICENSE"))

	def __repr__(self):
		return """
Type license() to see the full license text
		"""

class Clear:
	def __init__(self, console):
		self.console = console
	
	def __call__(self):
		self.console.clear()
		self.console.prompt()
	
	def __repr__(self):
		self.console.clear()
		self.console.prompt()
		return ""				
